Introduction to Artificial Neural Networks (ANN)

Artificial Neural Networks (ANN) are computing systems inspired by the biological neural networks of animal brains. These models are designed to simulate the way the human brain processes information, making them exceptionally good at modeling and solving complex problems by learning from data.

Applications of ANNs

ANNs are applied in a wide range of real-world scenarios, some of which include:

  • Netflix content recommendation: Uses ANNs to analyze your viewing history and preferences to recommend movies and shows you’re likely to enjoy.
  • Instagram feed: Employs deep learning algorithms to personalize your feed, showing you content that is more relevant to your interests.
  • Handwritten digit recognition: ANNs can identify and classify handwritten numbers, a technology used in document digitization and character recognition.
  • FaceID technology: Utilizes advanced ANNs for secure and accurate facial recognition, allowing you to unlock your device by simply looking at it.

These examples demonstrate the versatility and the ability of ANNs to learn from large datasets and perform tasks that would require human intelligence.

Exercise 1: Did I Pass the Subject?

This exercise focuses on using an ANN to predict whether a student has passed a subject based on their academic features, such as grades obtained in assignments, midterm exams, and class attendance.

1. Install Packages and Call Libraries

Before we start working with artificial neural networks in R, it’s crucial to have the neuralnet package installed and loaded. This package allows us to efficiently train and simulate artificial neural networks in R with a relatively straightforward syntax. The following code chunk first checks if the neuralnet package is installed, installing it if necessary. Then, it loads the package using library(neuralnet), enabling us to access its functions and use them in our analysis.

Ensure you have an active internet connection if you need to install the package, as install.packages will download the package from CRAN.

# install.packages("dplyr")
# install.packages("neuralnet")
library(dplyr)
## 
## Attaching package: 'dplyr'
## The following objects are masked from 'package:stats':
## 
##     filter, lag
## The following objects are masked from 'package:base':
## 
##     intersect, setdiff, setequal, union
library(neuralnet)
## 
## Attaching package: 'neuralnet'
## The following object is masked from 'package:dplyr':
## 
##     compute

2. Obtain Data

We are constructing a data frame named df that contains three columns: exam, proyect, and status. Each of these columns represents different aspects of a dataset that could be related to student assessments or project evaluations:

  • The exam column contains numeric scores or values associated with exams, with each entry representing a different individual or instance.

  • The proyect column (presumably intended to spell “project”) includes scores or values related to projects. Like the exam column, each entry here pertains to a different individual or instance.

  • The status column uses binary values (1 or 0) to indicate a particular state or condition for each entry. This could represent a pass/fail status, completion, or any other binary indicator relevant to the context of the data.

The code below combines these vectors into a single data frame, providing a structured and tabular representation of the data for further analysis or visualization.

exam<-c(20,10,30,20,80,30)
proyect<-c(90,20,40,50,50,80)
status<-c(1,0,0,0,0,1)

df<-data.frame(exam,proyect,status)

3. Generate Neural Network

We’re applying a neural network model to predict the status based on exam and proyect scores using the neuralnet package. This model helps us understand the relationship between exam/project scores and their status outcomes. After training the model with nn1 <- neuralnet(status ~., data=df), we visualize it using plot(nn1, rep="best") to inspect the network structure and how inputs are related to the prediction.

nn1 <- neuralnet(status ~., data=df)
plot(nn1, rep="best")

4. Predict Results

Now that our neural network model is trained, we proceed to evaluate its predictive performance using a new set of exam and project scores. This step is essential for assessing the model’s ability to generalize and make accurate predictions on data it hasn’t seen before. By inputting these new scores into the model, we aim to predict their corresponding statuses, providing insights into how well our model can apply learned patterns to real-world or hypothetical scenarios. This evaluation phase is crucial for understanding the practical applicability of our neural network in predicting outcomes based on exam and project performances.

exam_test<-c(30,40,85)
proyect_test<-c(85,50,40)
test1 <- data_frame(exam_test,proyect_test)
## Warning: `data_frame()` was deprecated in tibble 1.1.0.
## ℹ Please use `tibble()` instead.
## This warning is displayed once every 8 hours.
## Call `lifecycle::last_lifecycle_warnings()` to see where this warning was
## generated.
prediction <- compute(nn1,test1)

prediction$net.result
##             [,1]
## [1,]  1.01126893
## [2,]  0.01946212
## [3,] -0.01891719

1. Install Packages and Call Libraries

Before diving into our analysis, it’s crucial to ensure that all necessary packages are installed and loaded into our R environment. This step lays the groundwork for a smooth analysis process, allowing us to utilize various functions and tools provided by these packages. Specifically, we focus on two packages:

  • readr: Facilitates efficient reading of rectangular data, such as CSV files. Its fast and friendly syntax makes it a go-to choice for data import in R.
  • caret: Stands for Classification And REgression Training. This package provides a comprehensive suite of tools to create complex predictive models. It simplifies the process of training and tuning machine learning models, making it invaluable for predictive analytics.

By loading these libraries, we ensure that our R session is equipped with the necessary tools for data importation, preprocessing, and advanced modeling techniques.

# install.packages("readr")
# install.packages("caret")
library(readr)
library(caret)

2. Obtain Data

After setting up our environment with the necessary libraries, our next step involves loading and preparing the dataset for analysis. We’re focusing on a dataset named cancer_de_mama.csv, which includes data pertinent to breast cancer diagnosis. The preparation process involves several key steps to ensure the data is suitable for training a machine learning model:

  • Loading the Dataset: We use the read_csv function from the readr package to load the cancer_de_mama.csv file. This function is optimized for fast and efficient loading of CSV files into R.

  • Encoding the Diagnosis: The dataset includes a diagnosis column with categorical values (M for malignant, B for benign). We convert these into binary numeric values (1 for malignant, 0 for benign) to facilitate the modeling process. This encoding simplifies the use of algorithms that require numerical input.

  • Splitting the Data: To evaluate our model’s performance accurately, we split the dataset into training and testing sets. Using the createDataPartition function from the caret package, we allocate 75% of the data for training and reserve 25% for testing. This split is based on the diagnosis column to ensure that both sets are representative of the overall dataset.

  • Reproducibility: We set a seed before splitting the data to ensure that our results are reproducible. This step is crucial for scientific rigor and allows others to replicate our analysis exactly.

The following code snippet illustrates these steps:

breast_cancer <- read_csv("cancer_de_mama.csv")
breast_cancer$diagnosis <- ifelse(breast_cancer$diagnosis == "M", 1, 0)

# Create indices for a 75% train and 25% test split
set.seed(123) # Setting seed for reproducibility
trainIndex <- createDataPartition(breast_cancer$diagnosis, p = 0.75, 
                                  list = FALSE, 
                                  times = 1)

# Split the data into training and testing sets
trainSet <- breast_cancer[trainIndex, ]
testSet <- breast_cancer[-trainIndex, ]

head(breast_cancer)
## # A tibble: 6 × 31
##   diagnosis radius_mean texture_mean perimeter_mean area_mean smoothness_mean
##       <dbl>       <dbl>        <dbl>          <dbl>     <dbl>           <dbl>
## 1         1        18.0         10.4          123.      1001           0.118 
## 2         1        20.6         17.8          133.      1326           0.0847
## 3         1        19.7         21.2          130       1203           0.110 
## 4         1        11.4         20.4           77.6      386.          0.142 
## 5         1        20.3         14.3          135.      1297           0.100 
## 6         1        12.4         15.7           82.6      477.          0.128 
## # ℹ 25 more variables: compactness_mean <dbl>, concavity_mean <dbl>,
## #   `concave points_mean` <dbl>, symmetry_mean <dbl>,
## #   fractal_dimension_mean <dbl>, radius_se <dbl>, texture_se <dbl>,
## #   perimeter_se <dbl>, area_se <dbl>, smoothness_se <dbl>,
## #   compactness_se <dbl>, concavity_se <dbl>, `concave points_se` <dbl>,
## #   symmetry_se <dbl>, fractal_dimension_se <dbl>, radius_worst <dbl>,
## #   texture_worst <dbl>, perimeter_worst <dbl>, area_worst <dbl>, …

3. Clean Data

After splitting the breast_cancer dataset into training and testing sets, further refinements are made to ensure smooth model training and evaluation:

  • Standardizing Variable Names: To prevent potential issues with variable names that might not conform to R’s variable naming conventions (such as spaces or special characters), we use make.names with the unique = TRUE parameter. This function modifies the names in both training and testing datasets to ensure they are valid R identifiers and unique. This step is crucial for avoiding errors during model training and evaluation.

  • Preparing a Blind Testing Set: To evaluate the model’s predictive performance objectively, we create a ‘blind’ testing set. This version of the testing set excludes the diagnosis column, which is the outcome variable our model aims to predict. The absence of this variable ensures that our model predictions are made without access to the true outcomes, mimicking a real-world scenario where the model would be used to predict unknown cases.

The code modifications applied to the variable names and the preparation of the blind testing set are foundational for the subsequent modeling steps. They help in ensuring that our analysis pipeline is robust and that the evaluation of the model’s performance is as realistic as possible.

names(trainSet) <- make.names(names(trainSet), unique = TRUE)

names(testSet) <- make.names(names(trainSet), unique = TRUE)
testSetBlind <- subset(testSet, select = -diagnosis)

4. Generate Neural Network

With our data preprocessed and split into training and testing sets, we proceed to train a neural network model. Our objective is to predict the binary diagnosis outcome (malignant or benign) based on a range of input features. The model is configured with a single hidden layer consisting of five neurons, a setup aimed at capturing the underlying complexities in the data without overly complicating the model.

Following the model training, we visualize the neural network. This visualization serves as a crucial step for understanding the model’s structure, including how inputs are processed through hidden layers to produce the final prediction. It provides us with a graphical representation of the model, showcasing the neurons, layers, and their interconnections.

This trained model and its visualization allow us to closely examine the neural network’s architecture and better understand the factors influencing its predictions. It sets the stage for the next steps, where we will assess the model’s performance on unseen data and gauge its predictive accuracy.

nn2 <- neuralnet(diagnosis ~ ., data=trainSet, hidden=c(5), linear.output=FALSE)
plot(nn2, rep="best")

5. Predict Results

After predicting the outcomes for our blind testing set using the neural network model, we proceed to assess the model’s performance through several key metrics: accuracy, recall, and specificity. These metrics collectively offer a comprehensive view of the model’s predictive capabilities:

  • Accuracy provides a high-level overview of the model’s overall performance, indicating how often it predicts correctly.
  • Recall is especially important in medical diagnostics, as it measures the model’s ability to identify all relevant cases of malignancy.
  • Specificity complements recall by assessing the model’s proficiency in correctly identifying benign cases, thus avoiding unnecessary alarm.

The computation of these metrics is based on a confusion matrix, which contrasts the predicted labels against the actual labels from the testing set. This analysis yields valuable insights into the model’s strengths and areas for improvement, especially in the context of breast cancer diagnosis where the balance between sensitivity and specificity is critical.

prediction <- compute(nn2,testSetBlind)

predicted_labels <- ifelse(prediction$net.result > 0.5, 1, 0)
true_labels <- testSet$diagnosis
conf_matrix <- table(Predicted = predicted_labels, Actual = true_labels)

# Accuracy Metrics
accuracy <- sum(diag(conf_matrix)) / sum(conf_matrix)
recall <- conf_matrix[2,2] / sum(conf_matrix[2,])
specificity <- conf_matrix[1,1] / sum(conf_matrix[1,])

cat("Accuracy:", accuracy, "\n")
## Accuracy: 0.9859155
cat("Recall:", recall, "\n")
## Recall: 1
cat("Specificity:", specificity, "\n")
## Specificity: 0.9753086
LS0tCnRpdGxlOiAiUmVkZXMgTmV1cm9uYWxlcyIKYXV0aG9yOiAiRGF2aWQgRG9taW5ndWV6IC0gQTAxNTcwOTc1IgpkYXRlOiAiMjAyNC0wMi0yMiIKb3V0cHV0OiAKICBodG1sX2RvY3VtZW50OiAKICAgIHRvYzogVFJVRQogICAgdG9jX2Zsb2F0OiBUUlVFCiAgICBjb2RlX2Rvd25sb2FkOiBUUlVFCi0tLQoKIVtdKC9Vc2Vycy9kYXZpZGRydW1zMTgwL1RlYy9BTk4vMSotZUxqUFk3VUdTb1FoU3lXNXFDNmd3LmdpZikKCiMgSW50cm9kdWN0aW9uIHRvIEFydGlmaWNpYWwgTmV1cmFsIE5ldHdvcmtzIChBTk4pCgpBcnRpZmljaWFsIE5ldXJhbCBOZXR3b3JrcyAoQU5OKSBhcmUgY29tcHV0aW5nIHN5c3RlbXMgaW5zcGlyZWQgYnkgdGhlIGJpb2xvZ2ljYWwgbmV1cmFsIG5ldHdvcmtzIG9mIGFuaW1hbCBicmFpbnMuIFRoZXNlIG1vZGVscyBhcmUgZGVzaWduZWQgdG8gc2ltdWxhdGUgdGhlIHdheSB0aGUgaHVtYW4gYnJhaW4gcHJvY2Vzc2VzIGluZm9ybWF0aW9uLCBtYWtpbmcgdGhlbSBleGNlcHRpb25hbGx5IGdvb2QgYXQgbW9kZWxpbmcgYW5kIHNvbHZpbmcgY29tcGxleCBwcm9ibGVtcyBieSBsZWFybmluZyBmcm9tIGRhdGEuCgojIyBBcHBsaWNhdGlvbnMgb2YgQU5OcwoKQU5OcyBhcmUgYXBwbGllZCBpbiBhIHdpZGUgcmFuZ2Ugb2YgcmVhbC13b3JsZCBzY2VuYXJpb3MsIHNvbWUgb2Ygd2hpY2ggaW5jbHVkZToKCi0gKipOZXRmbGl4IGNvbnRlbnQgcmVjb21tZW5kYXRpb24qKjogVXNlcyBBTk5zIHRvIGFuYWx5emUgeW91ciB2aWV3aW5nIGhpc3RvcnkgYW5kIHByZWZlcmVuY2VzIHRvIHJlY29tbWVuZCBtb3ZpZXMgYW5kIHNob3dzIHlvdSdyZSBsaWtlbHkgdG8gZW5qb3kuCi0gKipJbnN0YWdyYW0gZmVlZCoqOiBFbXBsb3lzIGRlZXAgbGVhcm5pbmcgYWxnb3JpdGhtcyB0byBwZXJzb25hbGl6ZSB5b3VyIGZlZWQsIHNob3dpbmcgeW91IGNvbnRlbnQgdGhhdCBpcyBtb3JlIHJlbGV2YW50IHRvIHlvdXIgaW50ZXJlc3RzLgotICoqSGFuZHdyaXR0ZW4gZGlnaXQgcmVjb2duaXRpb24qKjogQU5OcyBjYW4gaWRlbnRpZnkgYW5kIGNsYXNzaWZ5IGhhbmR3cml0dGVuIG51bWJlcnMsIGEgdGVjaG5vbG9neSB1c2VkIGluIGRvY3VtZW50IGRpZ2l0aXphdGlvbiBhbmQgY2hhcmFjdGVyIHJlY29nbml0aW9uLgotICoqRmFjZUlEIHRlY2hub2xvZ3kqKjogVXRpbGl6ZXMgYWR2YW5jZWQgQU5OcyBmb3Igc2VjdXJlIGFuZCBhY2N1cmF0ZSBmYWNpYWwgcmVjb2duaXRpb24sIGFsbG93aW5nIHlvdSB0byB1bmxvY2sgeW91ciBkZXZpY2UgYnkgc2ltcGx5IGxvb2tpbmcgYXQgaXQuCgpUaGVzZSBleGFtcGxlcyBkZW1vbnN0cmF0ZSB0aGUgdmVyc2F0aWxpdHkgYW5kIHRoZSBhYmlsaXR5IG9mIEFOTnMgdG8gbGVhcm4gZnJvbSBsYXJnZSBkYXRhc2V0cyBhbmQgcGVyZm9ybSB0YXNrcyB0aGF0IHdvdWxkIHJlcXVpcmUgaHVtYW4gaW50ZWxsaWdlbmNlLgoKIyMgRXhlcmNpc2UgMTogRGlkIEkgUGFzcyB0aGUgU3ViamVjdD8KClRoaXMgZXhlcmNpc2UgZm9jdXNlcyBvbiB1c2luZyBhbiBBTk4gdG8gcHJlZGljdCB3aGV0aGVyIGEgc3R1ZGVudCBoYXMgcGFzc2VkIGEgc3ViamVjdCBiYXNlZCBvbiB0aGVpciBhY2FkZW1pYyBmZWF0dXJlcywgc3VjaCBhcyBncmFkZXMgb2J0YWluZWQgaW4gYXNzaWdubWVudHMsIG1pZHRlcm0gZXhhbXMsIGFuZCBjbGFzcyBhdHRlbmRhbmNlLgoKIyMgPHNwYW4gc3R5bGU9ImNvbG9yOiBibHVlOyI+MS4gSW5zdGFsbCBQYWNrYWdlcyBhbmQgQ2FsbCBMaWJyYXJpZXM8L3NwYW4+CkJlZm9yZSB3ZSBzdGFydCB3b3JraW5nIHdpdGggYXJ0aWZpY2lhbCBuZXVyYWwgbmV0d29ya3MgaW4gUiwgaXQncyBjcnVjaWFsIHRvIGhhdmUgdGhlIGBuZXVyYWxuZXRgIHBhY2thZ2UgaW5zdGFsbGVkIGFuZCBsb2FkZWQuIFRoaXMgcGFja2FnZSBhbGxvd3MgdXMgdG8gZWZmaWNpZW50bHkgdHJhaW4gYW5kIHNpbXVsYXRlIGFydGlmaWNpYWwgbmV1cmFsIG5ldHdvcmtzIGluIFIgd2l0aCBhIHJlbGF0aXZlbHkgc3RyYWlnaHRmb3J3YXJkIHN5bnRheC4gVGhlIGZvbGxvd2luZyBjb2RlIGNodW5rIGZpcnN0IGNoZWNrcyBpZiB0aGUgYG5ldXJhbG5ldGAgcGFja2FnZSBpcyBpbnN0YWxsZWQsIGluc3RhbGxpbmcgaXQgaWYgbmVjZXNzYXJ5LiBUaGVuLCBpdCBsb2FkcyB0aGUgcGFja2FnZSB1c2luZyBgbGlicmFyeShuZXVyYWxuZXQpYCwgZW5hYmxpbmcgdXMgdG8gYWNjZXNzIGl0cyBmdW5jdGlvbnMgYW5kIHVzZSB0aGVtIGluIG91ciBhbmFseXNpcy4KCkVuc3VyZSB5b3UgaGF2ZSBhbiBhY3RpdmUgaW50ZXJuZXQgY29ubmVjdGlvbiBpZiB5b3UgbmVlZCB0byBpbnN0YWxsIHRoZSBwYWNrYWdlLCBhcyBgaW5zdGFsbC5wYWNrYWdlc2Agd2lsbCBkb3dubG9hZCB0aGUgcGFja2FnZSBmcm9tIENSQU4uCgoKYGBge3J9CiMgaW5zdGFsbC5wYWNrYWdlcygiZHBseXIiKQojIGluc3RhbGwucGFja2FnZXMoIm5ldXJhbG5ldCIpCmxpYnJhcnkoZHBseXIpCmxpYnJhcnkobmV1cmFsbmV0KQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogYmx1ZTsiPjIuIE9idGFpbiBEYXRhPC9zcGFuPgpXZSBhcmUgY29uc3RydWN0aW5nIGEgZGF0YSBmcmFtZSBuYW1lZCBgZGZgIHRoYXQgY29udGFpbnMgdGhyZWUgY29sdW1uczogYGV4YW1gLCBgcHJveWVjdGAsIGFuZCBgc3RhdHVzYC4gRWFjaCBvZiB0aGVzZSBjb2x1bW5zIHJlcHJlc2VudHMgZGlmZmVyZW50IGFzcGVjdHMgb2YgYSBkYXRhc2V0IHRoYXQgY291bGQgYmUgcmVsYXRlZCB0byBzdHVkZW50IGFzc2Vzc21lbnRzIG9yIHByb2plY3QgZXZhbHVhdGlvbnM6CgotIFRoZSBgZXhhbWAgY29sdW1uIGNvbnRhaW5zIG51bWVyaWMgc2NvcmVzIG9yIHZhbHVlcyBhc3NvY2lhdGVkIHdpdGggZXhhbXMsIHdpdGggZWFjaCBlbnRyeSByZXByZXNlbnRpbmcgYSBkaWZmZXJlbnQgaW5kaXZpZHVhbCBvciBpbnN0YW5jZS4KCi0gVGhlIGBwcm95ZWN0YCBjb2x1bW4gKHByZXN1bWFibHkgaW50ZW5kZWQgdG8gc3BlbGwgInByb2plY3QiKSBpbmNsdWRlcyBzY29yZXMgb3IgdmFsdWVzIHJlbGF0ZWQgdG8gcHJvamVjdHMuIExpa2UgdGhlIGBleGFtYCBjb2x1bW4sIGVhY2ggZW50cnkgaGVyZSBwZXJ0YWlucyB0byBhIGRpZmZlcmVudCBpbmRpdmlkdWFsIG9yIGluc3RhbmNlLgoKLSBUaGUgYHN0YXR1c2AgY29sdW1uIHVzZXMgYmluYXJ5IHZhbHVlcyAoMSBvciAwKSB0byBpbmRpY2F0ZSBhIHBhcnRpY3VsYXIgc3RhdGUgb3IgY29uZGl0aW9uIGZvciBlYWNoIGVudHJ5LiBUaGlzIGNvdWxkIHJlcHJlc2VudCBhIHBhc3MvZmFpbCBzdGF0dXMsIGNvbXBsZXRpb24sIG9yIGFueSBvdGhlciBiaW5hcnkgaW5kaWNhdG9yIHJlbGV2YW50IHRvIHRoZSBjb250ZXh0IG9mIHRoZSBkYXRhLgoKVGhlIGNvZGUgYmVsb3cgY29tYmluZXMgdGhlc2UgdmVjdG9ycyBpbnRvIGEgc2luZ2xlIGRhdGEgZnJhbWUsIHByb3ZpZGluZyBhIHN0cnVjdHVyZWQgYW5kIHRhYnVsYXIgcmVwcmVzZW50YXRpb24gb2YgdGhlIGRhdGEgZm9yIGZ1cnRoZXIgYW5hbHlzaXMgb3IgdmlzdWFsaXphdGlvbi4KCgpgYGB7cn0KZXhhbTwtYygyMCwxMCwzMCwyMCw4MCwzMCkKcHJveWVjdDwtYyg5MCwyMCw0MCw1MCw1MCw4MCkKc3RhdHVzPC1jKDEsMCwwLDAsMCwxKQoKZGY8LWRhdGEuZnJhbWUoZXhhbSxwcm95ZWN0LHN0YXR1cykKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij4zLiBHZW5lcmF0ZSBOZXVyYWwgTmV0d29yazwvc3Bhbj4KV2UncmUgYXBwbHlpbmcgYSBuZXVyYWwgbmV0d29yayBtb2RlbCB0byBwcmVkaWN0IHRoZSBgc3RhdHVzYCBiYXNlZCBvbiBgZXhhbWAgYW5kIGBwcm95ZWN0YCBzY29yZXMgdXNpbmcgdGhlIGBuZXVyYWxuZXRgIHBhY2thZ2UuIFRoaXMgbW9kZWwgaGVscHMgdXMgdW5kZXJzdGFuZCB0aGUgcmVsYXRpb25zaGlwIGJldHdlZW4gZXhhbS9wcm9qZWN0IHNjb3JlcyBhbmQgdGhlaXIgc3RhdHVzIG91dGNvbWVzLiBBZnRlciB0cmFpbmluZyB0aGUgbW9kZWwgd2l0aCBgbm4xIDwtIG5ldXJhbG5ldChzdGF0dXMgfi4sIGRhdGE9ZGYpYCwgd2UgdmlzdWFsaXplIGl0IHVzaW5nIGBwbG90KG5uMSwgcmVwPSJiZXN0IilgIHRvIGluc3BlY3QgdGhlIG5ldHdvcmsgc3RydWN0dXJlIGFuZCBob3cgaW5wdXRzIGFyZSByZWxhdGVkIHRvIHRoZSBwcmVkaWN0aW9uLgoKYGBge3J9Cm5uMSA8LSBuZXVyYWxuZXQoc3RhdHVzIH4uLCBkYXRhPWRmKQpwbG90KG5uMSwgcmVwPSJiZXN0IikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij40LiBQcmVkaWN0IFJlc3VsdHM8L3NwYW4+Ck5vdyB0aGF0IG91ciBuZXVyYWwgbmV0d29yayBtb2RlbCBpcyB0cmFpbmVkLCB3ZSBwcm9jZWVkIHRvIGV2YWx1YXRlIGl0cyBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIHVzaW5nIGEgbmV3IHNldCBvZiBleGFtIGFuZCBwcm9qZWN0IHNjb3Jlcy4gVGhpcyBzdGVwIGlzIGVzc2VudGlhbCBmb3IgYXNzZXNzaW5nIHRoZSBtb2RlbCdzIGFiaWxpdHkgdG8gZ2VuZXJhbGl6ZSBhbmQgbWFrZSBhY2N1cmF0ZSBwcmVkaWN0aW9ucyBvbiBkYXRhIGl0IGhhc24ndCBzZWVuIGJlZm9yZS4gQnkgaW5wdXR0aW5nIHRoZXNlIG5ldyBzY29yZXMgaW50byB0aGUgbW9kZWwsIHdlIGFpbSB0byBwcmVkaWN0IHRoZWlyIGNvcnJlc3BvbmRpbmcgc3RhdHVzZXMsIHByb3ZpZGluZyBpbnNpZ2h0cyBpbnRvIGhvdyB3ZWxsIG91ciBtb2RlbCBjYW4gYXBwbHkgbGVhcm5lZCBwYXR0ZXJucyB0byByZWFsLXdvcmxkIG9yIGh5cG90aGV0aWNhbCBzY2VuYXJpb3MuIFRoaXMgZXZhbHVhdGlvbiBwaGFzZSBpcyBjcnVjaWFsIGZvciB1bmRlcnN0YW5kaW5nIHRoZSBwcmFjdGljYWwgYXBwbGljYWJpbGl0eSBvZiBvdXIgbmV1cmFsIG5ldHdvcmsgaW4gcHJlZGljdGluZyBvdXRjb21lcyBiYXNlZCBvbiBleGFtIGFuZCBwcm9qZWN0IHBlcmZvcm1hbmNlcy4KCgpgYGB7cn0KZXhhbV90ZXN0PC1jKDMwLDQwLDg1KQpwcm95ZWN0X3Rlc3Q8LWMoODUsNTAsNDApCnRlc3QxIDwtIGRhdGFfZnJhbWUoZXhhbV90ZXN0LHByb3llY3RfdGVzdCkKcHJlZGljdGlvbiA8LSBjb21wdXRlKG5uMSx0ZXN0MSkKCnByZWRpY3Rpb24kbmV0LnJlc3VsdApgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+MS4gSW5zdGFsbCBQYWNrYWdlcyBhbmQgQ2FsbCBMaWJyYXJpZXM8L3NwYW4+CkJlZm9yZSBkaXZpbmcgaW50byBvdXIgYW5hbHlzaXMsIGl0J3MgY3J1Y2lhbCB0byBlbnN1cmUgdGhhdCBhbGwgbmVjZXNzYXJ5IHBhY2thZ2VzIGFyZSBpbnN0YWxsZWQgYW5kIGxvYWRlZCBpbnRvIG91ciBSIGVudmlyb25tZW50LiBUaGlzIHN0ZXAgbGF5cyB0aGUgZ3JvdW5kd29yayBmb3IgYSBzbW9vdGggYW5hbHlzaXMgcHJvY2VzcywgYWxsb3dpbmcgdXMgdG8gdXRpbGl6ZSB2YXJpb3VzIGZ1bmN0aW9ucyBhbmQgdG9vbHMgcHJvdmlkZWQgYnkgdGhlc2UgcGFja2FnZXMuIFNwZWNpZmljYWxseSwgd2UgZm9jdXMgb24gdHdvIHBhY2thZ2VzOgoKLSAqKmByZWFkcmAqKjogRmFjaWxpdGF0ZXMgZWZmaWNpZW50IHJlYWRpbmcgb2YgcmVjdGFuZ3VsYXIgZGF0YSwgc3VjaCBhcyBDU1YgZmlsZXMuIEl0cyBmYXN0IGFuZCBmcmllbmRseSBzeW50YXggbWFrZXMgaXQgYSBnby10byBjaG9pY2UgZm9yIGRhdGEgaW1wb3J0IGluIFIuCi0gKipgY2FyZXRgKio6IFN0YW5kcyBmb3IgKkNsYXNzaWZpY2F0aW9uIEFuZCBSRWdyZXNzaW9uIFRyYWluaW5nKi4gVGhpcyBwYWNrYWdlIHByb3ZpZGVzIGEgY29tcHJlaGVuc2l2ZSBzdWl0ZSBvZiB0b29scyB0byBjcmVhdGUgY29tcGxleCBwcmVkaWN0aXZlIG1vZGVscy4gSXQgc2ltcGxpZmllcyB0aGUgcHJvY2VzcyBvZiB0cmFpbmluZyBhbmQgdHVuaW5nIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWxzLCBtYWtpbmcgaXQgaW52YWx1YWJsZSBmb3IgcHJlZGljdGl2ZSBhbmFseXRpY3MuCgpCeSBsb2FkaW5nIHRoZXNlIGxpYnJhcmllcywgd2UgZW5zdXJlIHRoYXQgb3VyIFIgc2Vzc2lvbiBpcyBlcXVpcHBlZCB3aXRoIHRoZSBuZWNlc3NhcnkgdG9vbHMgZm9yIGRhdGEgaW1wb3J0YXRpb24sIHByZXByb2Nlc3NpbmcsIGFuZCBhZHZhbmNlZCBtb2RlbGluZyB0ZWNobmlxdWVzLgoKYGBge3IgbWVzc2FnZT1GQUxTRSwgd2FybmluZz1GQUxTRX0KIyBpbnN0YWxsLnBhY2thZ2VzKCJyZWFkciIpCiMgaW5zdGFsbC5wYWNrYWdlcygiY2FyZXQiKQpsaWJyYXJ5KHJlYWRyKQpsaWJyYXJ5KGNhcmV0KQpgYGAKCiMjIDxzcGFuIHN0eWxlPSJjb2xvcjogcmVkOyI+Mi4gT2J0YWluIERhdGE8L3NwYW4+CkFmdGVyIHNldHRpbmcgdXAgb3VyIGVudmlyb25tZW50IHdpdGggdGhlIG5lY2Vzc2FyeSBsaWJyYXJpZXMsIG91ciBuZXh0IHN0ZXAgaW52b2x2ZXMgbG9hZGluZyBhbmQgcHJlcGFyaW5nIHRoZSBkYXRhc2V0IGZvciBhbmFseXNpcy4gV2UncmUgZm9jdXNpbmcgb24gYSBkYXRhc2V0IG5hbWVkIGBjYW5jZXJfZGVfbWFtYS5jc3ZgLCB3aGljaCBpbmNsdWRlcyBkYXRhIHBlcnRpbmVudCB0byBicmVhc3QgY2FuY2VyIGRpYWdub3Npcy4gVGhlIHByZXBhcmF0aW9uIHByb2Nlc3MgaW52b2x2ZXMgc2V2ZXJhbCBrZXkgc3RlcHMgdG8gZW5zdXJlIHRoZSBkYXRhIGlzIHN1aXRhYmxlIGZvciB0cmFpbmluZyBhIG1hY2hpbmUgbGVhcm5pbmcgbW9kZWw6CgotICoqTG9hZGluZyB0aGUgRGF0YXNldCoqOiBXZSB1c2UgdGhlIGByZWFkX2NzdmAgZnVuY3Rpb24gZnJvbSB0aGUgYHJlYWRyYCBwYWNrYWdlIHRvIGxvYWQgdGhlIGBjYW5jZXJfZGVfbWFtYS5jc3ZgIGZpbGUuIFRoaXMgZnVuY3Rpb24gaXMgb3B0aW1pemVkIGZvciBmYXN0IGFuZCBlZmZpY2llbnQgbG9hZGluZyBvZiBDU1YgZmlsZXMgaW50byBSLgoKLSAqKkVuY29kaW5nIHRoZSBEaWFnbm9zaXMqKjogVGhlIGRhdGFzZXQgaW5jbHVkZXMgYSBkaWFnbm9zaXMgY29sdW1uIHdpdGggY2F0ZWdvcmljYWwgdmFsdWVzIChgTWAgZm9yIG1hbGlnbmFudCwgYEJgIGZvciBiZW5pZ24pLiBXZSBjb252ZXJ0IHRoZXNlIGludG8gYmluYXJ5IG51bWVyaWMgdmFsdWVzICgxIGZvciBtYWxpZ25hbnQsIDAgZm9yIGJlbmlnbikgdG8gZmFjaWxpdGF0ZSB0aGUgbW9kZWxpbmcgcHJvY2Vzcy4gVGhpcyBlbmNvZGluZyBzaW1wbGlmaWVzIHRoZSB1c2Ugb2YgYWxnb3JpdGhtcyB0aGF0IHJlcXVpcmUgbnVtZXJpY2FsIGlucHV0LgoKLSAqKlNwbGl0dGluZyB0aGUgRGF0YSoqOiBUbyBldmFsdWF0ZSBvdXIgbW9kZWwncyBwZXJmb3JtYW5jZSBhY2N1cmF0ZWx5LCB3ZSBzcGxpdCB0aGUgZGF0YXNldCBpbnRvIHRyYWluaW5nIGFuZCB0ZXN0aW5nIHNldHMuIFVzaW5nIHRoZSBgY3JlYXRlRGF0YVBhcnRpdGlvbmAgZnVuY3Rpb24gZnJvbSB0aGUgYGNhcmV0YCBwYWNrYWdlLCB3ZSBhbGxvY2F0ZSA3NSUgb2YgdGhlIGRhdGEgZm9yIHRyYWluaW5nIGFuZCByZXNlcnZlIDI1JSBmb3IgdGVzdGluZy4gVGhpcyBzcGxpdCBpcyBiYXNlZCBvbiB0aGUgZGlhZ25vc2lzIGNvbHVtbiB0byBlbnN1cmUgdGhhdCBib3RoIHNldHMgYXJlIHJlcHJlc2VudGF0aXZlIG9mIHRoZSBvdmVyYWxsIGRhdGFzZXQuCgotICoqUmVwcm9kdWNpYmlsaXR5Kio6IFdlIHNldCBhIHNlZWQgYmVmb3JlIHNwbGl0dGluZyB0aGUgZGF0YSB0byBlbnN1cmUgdGhhdCBvdXIgcmVzdWx0cyBhcmUgcmVwcm9kdWNpYmxlLiBUaGlzIHN0ZXAgaXMgY3J1Y2lhbCBmb3Igc2NpZW50aWZpYyByaWdvciBhbmQgYWxsb3dzIG90aGVycyB0byByZXBsaWNhdGUgb3VyIGFuYWx5c2lzIGV4YWN0bHkuCgpUaGUgZm9sbG93aW5nIGNvZGUgc25pcHBldCBpbGx1c3RyYXRlcyB0aGVzZSBzdGVwczoKCmBgYHtyIG1lc3NhZ2U9RkFMU0UsIHdhcm5pbmc9RkFMU0V9CmJyZWFzdF9jYW5jZXIgPC0gcmVhZF9jc3YoImNhbmNlcl9kZV9tYW1hLmNzdiIpCmJyZWFzdF9jYW5jZXIkZGlhZ25vc2lzIDwtIGlmZWxzZShicmVhc3RfY2FuY2VyJGRpYWdub3NpcyA9PSAiTSIsIDEsIDApCgojIENyZWF0ZSBpbmRpY2VzIGZvciBhIDc1JSB0cmFpbiBhbmQgMjUlIHRlc3Qgc3BsaXQKc2V0LnNlZWQoMTIzKSAjIFNldHRpbmcgc2VlZCBmb3IgcmVwcm9kdWNpYmlsaXR5CnRyYWluSW5kZXggPC0gY3JlYXRlRGF0YVBhcnRpdGlvbihicmVhc3RfY2FuY2VyJGRpYWdub3NpcywgcCA9IDAuNzUsIAogICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgbGlzdCA9IEZBTFNFLCAKICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHRpbWVzID0gMSkKCiMgU3BsaXQgdGhlIGRhdGEgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzCnRyYWluU2V0IDwtIGJyZWFzdF9jYW5jZXJbdHJhaW5JbmRleCwgXQp0ZXN0U2V0IDwtIGJyZWFzdF9jYW5jZXJbLXRyYWluSW5kZXgsIF0KCmhlYWQoYnJlYXN0X2NhbmNlcikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPjMuIENsZWFuIERhdGE8L3NwYW4+CkFmdGVyIHNwbGl0dGluZyB0aGUgYGJyZWFzdF9jYW5jZXJgIGRhdGFzZXQgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLCBmdXJ0aGVyIHJlZmluZW1lbnRzIGFyZSBtYWRlIHRvIGVuc3VyZSBzbW9vdGggbW9kZWwgdHJhaW5pbmcgYW5kIGV2YWx1YXRpb246CgotICoqU3RhbmRhcmRpemluZyBWYXJpYWJsZSBOYW1lcyoqOiBUbyBwcmV2ZW50IHBvdGVudGlhbCBpc3N1ZXMgd2l0aCB2YXJpYWJsZSBuYW1lcyB0aGF0IG1pZ2h0IG5vdCBjb25mb3JtIHRvIFIncyB2YXJpYWJsZSBuYW1pbmcgY29udmVudGlvbnMgKHN1Y2ggYXMgc3BhY2VzIG9yIHNwZWNpYWwgY2hhcmFjdGVycyksIHdlIHVzZSBgbWFrZS5uYW1lc2Agd2l0aCB0aGUgYHVuaXF1ZSA9IFRSVUVgIHBhcmFtZXRlci4gVGhpcyBmdW5jdGlvbiBtb2RpZmllcyB0aGUgbmFtZXMgaW4gYm90aCB0cmFpbmluZyBhbmQgdGVzdGluZyBkYXRhc2V0cyB0byBlbnN1cmUgdGhleSBhcmUgdmFsaWQgUiBpZGVudGlmaWVycyBhbmQgdW5pcXVlLiBUaGlzIHN0ZXAgaXMgY3J1Y2lhbCBmb3IgYXZvaWRpbmcgZXJyb3JzIGR1cmluZyBtb2RlbCB0cmFpbmluZyBhbmQgZXZhbHVhdGlvbi4KCi0gKipQcmVwYXJpbmcgYSBCbGluZCBUZXN0aW5nIFNldCoqOiBUbyBldmFsdWF0ZSB0aGUgbW9kZWwncyBwcmVkaWN0aXZlIHBlcmZvcm1hbmNlIG9iamVjdGl2ZWx5LCB3ZSBjcmVhdGUgYSAnYmxpbmQnIHRlc3Rpbmcgc2V0LiBUaGlzIHZlcnNpb24gb2YgdGhlIHRlc3Rpbmcgc2V0IGV4Y2x1ZGVzIHRoZSBgZGlhZ25vc2lzYCBjb2x1bW4sIHdoaWNoIGlzIHRoZSBvdXRjb21lIHZhcmlhYmxlIG91ciBtb2RlbCBhaW1zIHRvIHByZWRpY3QuIFRoZSBhYnNlbmNlIG9mIHRoaXMgdmFyaWFibGUgZW5zdXJlcyB0aGF0IG91ciBtb2RlbCBwcmVkaWN0aW9ucyBhcmUgbWFkZSB3aXRob3V0IGFjY2VzcyB0byB0aGUgdHJ1ZSBvdXRjb21lcywgbWltaWNraW5nIGEgcmVhbC13b3JsZCBzY2VuYXJpbyB3aGVyZSB0aGUgbW9kZWwgd291bGQgYmUgdXNlZCB0byBwcmVkaWN0IHVua25vd24gY2FzZXMuCgpUaGUgY29kZSBtb2RpZmljYXRpb25zIGFwcGxpZWQgdG8gdGhlIHZhcmlhYmxlIG5hbWVzIGFuZCB0aGUgcHJlcGFyYXRpb24gb2YgdGhlIGJsaW5kIHRlc3Rpbmcgc2V0IGFyZSBmb3VuZGF0aW9uYWwgZm9yIHRoZSBzdWJzZXF1ZW50IG1vZGVsaW5nIHN0ZXBzLiBUaGV5IGhlbHAgaW4gZW5zdXJpbmcgdGhhdCBvdXIgYW5hbHlzaXMgcGlwZWxpbmUgaXMgcm9idXN0IGFuZCB0aGF0IHRoZSBldmFsdWF0aW9uIG9mIHRoZSBtb2RlbCdzIHBlcmZvcm1hbmNlIGlzIGFzIHJlYWxpc3RpYyBhcyBwb3NzaWJsZS4KCmBgYHtyfQpuYW1lcyh0cmFpblNldCkgPC0gbWFrZS5uYW1lcyhuYW1lcyh0cmFpblNldCksIHVuaXF1ZSA9IFRSVUUpCgpuYW1lcyh0ZXN0U2V0KSA8LSBtYWtlLm5hbWVzKG5hbWVzKHRyYWluU2V0KSwgdW5pcXVlID0gVFJVRSkKdGVzdFNldEJsaW5kIDwtIHN1YnNldCh0ZXN0U2V0LCBzZWxlY3QgPSAtZGlhZ25vc2lzKQpgYGAKCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IHJlZDsiPjMuIEdlbmVyYXRlIE5ldXJhbCBOZXR3b3JrPC9zcGFuPgpXaXRoIG91ciBkYXRhIHByZXByb2Nlc3NlZCBhbmQgc3BsaXQgaW50byB0cmFpbmluZyBhbmQgdGVzdGluZyBzZXRzLCB3ZSBwcm9jZWVkIHRvIHRyYWluIGEgbmV1cmFsIG5ldHdvcmsgbW9kZWwuIE91ciBvYmplY3RpdmUgaXMgdG8gcHJlZGljdCB0aGUgYmluYXJ5IGRpYWdub3NpcyBvdXRjb21lIChtYWxpZ25hbnQgb3IgYmVuaWduKSBiYXNlZCBvbiBhIHJhbmdlIG9mIGlucHV0IGZlYXR1cmVzLiBUaGUgbW9kZWwgaXMgY29uZmlndXJlZCB3aXRoIGEgc2luZ2xlIGhpZGRlbiBsYXllciBjb25zaXN0aW5nIG9mIGZpdmUgbmV1cm9ucywgYSBzZXR1cCBhaW1lZCBhdCBjYXB0dXJpbmcgdGhlIHVuZGVybHlpbmcgY29tcGxleGl0aWVzIGluIHRoZSBkYXRhIHdpdGhvdXQgb3Zlcmx5IGNvbXBsaWNhdGluZyB0aGUgbW9kZWwuCgpGb2xsb3dpbmcgdGhlIG1vZGVsIHRyYWluaW5nLCB3ZSB2aXN1YWxpemUgdGhlIG5ldXJhbCBuZXR3b3JrLiBUaGlzIHZpc3VhbGl6YXRpb24gc2VydmVzIGFzIGEgY3J1Y2lhbCBzdGVwIGZvciB1bmRlcnN0YW5kaW5nIHRoZSBtb2RlbCdzIHN0cnVjdHVyZSwgaW5jbHVkaW5nIGhvdyBpbnB1dHMgYXJlIHByb2Nlc3NlZCB0aHJvdWdoIGhpZGRlbiBsYXllcnMgdG8gcHJvZHVjZSB0aGUgZmluYWwgcHJlZGljdGlvbi4gSXQgcHJvdmlkZXMgdXMgd2l0aCBhIGdyYXBoaWNhbCByZXByZXNlbnRhdGlvbiBvZiB0aGUgbW9kZWwsIHNob3djYXNpbmcgdGhlIG5ldXJvbnMsIGxheWVycywgYW5kIHRoZWlyIGludGVyY29ubmVjdGlvbnMuCgpUaGlzIHRyYWluZWQgbW9kZWwgYW5kIGl0cyB2aXN1YWxpemF0aW9uIGFsbG93IHVzIHRvIGNsb3NlbHkgZXhhbWluZSB0aGUgbmV1cmFsIG5ldHdvcmsncyBhcmNoaXRlY3R1cmUgYW5kIGJldHRlciB1bmRlcnN0YW5kIHRoZSBmYWN0b3JzIGluZmx1ZW5jaW5nIGl0cyBwcmVkaWN0aW9ucy4gSXQgc2V0cyB0aGUgc3RhZ2UgZm9yIHRoZSBuZXh0IHN0ZXBzLCB3aGVyZSB3ZSB3aWxsIGFzc2VzcyB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSBvbiB1bnNlZW4gZGF0YSBhbmQgZ2F1Z2UgaXRzIHByZWRpY3RpdmUgYWNjdXJhY3kuCmBgYHtyfQpubjIgPC0gbmV1cmFsbmV0KGRpYWdub3NpcyB+IC4sIGRhdGE9dHJhaW5TZXQsIGhpZGRlbj1jKDUpLCBsaW5lYXIub3V0cHV0PUZBTFNFKQpwbG90KG5uMiwgcmVwPSJiZXN0IikKYGBgCgojIyA8c3BhbiBzdHlsZT0iY29sb3I6IGJsdWU7Ij40LiBQcmVkaWN0IFJlc3VsdHM8L3NwYW4+CkFmdGVyIHByZWRpY3RpbmcgdGhlIG91dGNvbWVzIGZvciBvdXIgYmxpbmQgdGVzdGluZyBzZXQgdXNpbmcgdGhlIG5ldXJhbCBuZXR3b3JrIG1vZGVsLCB3ZSBwcm9jZWVkIHRvIGFzc2VzcyB0aGUgbW9kZWwncyBwZXJmb3JtYW5jZSB0aHJvdWdoIHNldmVyYWwga2V5IG1ldHJpY3M6IGFjY3VyYWN5LCByZWNhbGwsIGFuZCBzcGVjaWZpY2l0eS4gVGhlc2UgbWV0cmljcyBjb2xsZWN0aXZlbHkgb2ZmZXIgYSBjb21wcmVoZW5zaXZlIHZpZXcgb2YgdGhlIG1vZGVsJ3MgcHJlZGljdGl2ZSBjYXBhYmlsaXRpZXM6CgotICoqQWNjdXJhY3kqKiBwcm92aWRlcyBhIGhpZ2gtbGV2ZWwgb3ZlcnZpZXcgb2YgdGhlIG1vZGVsJ3Mgb3ZlcmFsbCBwZXJmb3JtYW5jZSwgaW5kaWNhdGluZyBob3cgb2Z0ZW4gaXQgcHJlZGljdHMgY29ycmVjdGx5LgotICoqUmVjYWxsKiogaXMgZXNwZWNpYWxseSBpbXBvcnRhbnQgaW4gbWVkaWNhbCBkaWFnbm9zdGljcywgYXMgaXQgbWVhc3VyZXMgdGhlIG1vZGVsJ3MgYWJpbGl0eSB0byBpZGVudGlmeSBhbGwgcmVsZXZhbnQgY2FzZXMgb2YgbWFsaWduYW5jeS4KLSAqKlNwZWNpZmljaXR5KiogY29tcGxlbWVudHMgcmVjYWxsIGJ5IGFzc2Vzc2luZyB0aGUgbW9kZWwncyBwcm9maWNpZW5jeSBpbiBjb3JyZWN0bHkgaWRlbnRpZnlpbmcgYmVuaWduIGNhc2VzLCB0aHVzIGF2b2lkaW5nIHVubmVjZXNzYXJ5IGFsYXJtLgoKVGhlIGNvbXB1dGF0aW9uIG9mIHRoZXNlIG1ldHJpY3MgaXMgYmFzZWQgb24gYSBjb25mdXNpb24gbWF0cml4LCB3aGljaCBjb250cmFzdHMgdGhlIHByZWRpY3RlZCBsYWJlbHMgYWdhaW5zdCB0aGUgYWN0dWFsIGxhYmVscyBmcm9tIHRoZSB0ZXN0aW5nIHNldC4gVGhpcyBhbmFseXNpcyB5aWVsZHMgdmFsdWFibGUgaW5zaWdodHMgaW50byB0aGUgbW9kZWwncyBzdHJlbmd0aHMgYW5kIGFyZWFzIGZvciBpbXByb3ZlbWVudCwgZXNwZWNpYWxseSBpbiB0aGUgY29udGV4dCBvZiBicmVhc3QgY2FuY2VyIGRpYWdub3NpcyB3aGVyZSB0aGUgYmFsYW5jZSBiZXR3ZWVuIHNlbnNpdGl2aXR5IGFuZCBzcGVjaWZpY2l0eSBpcyBjcml0aWNhbC4KCmBgYHtyfQpwcmVkaWN0aW9uIDwtIGNvbXB1dGUobm4yLHRlc3RTZXRCbGluZCkKCnByZWRpY3RlZF9sYWJlbHMgPC0gaWZlbHNlKHByZWRpY3Rpb24kbmV0LnJlc3VsdCA+IDAuNSwgMSwgMCkKdHJ1ZV9sYWJlbHMgPC0gdGVzdFNldCRkaWFnbm9zaXMKY29uZl9tYXRyaXggPC0gdGFibGUoUHJlZGljdGVkID0gcHJlZGljdGVkX2xhYmVscywgQWN0dWFsID0gdHJ1ZV9sYWJlbHMpCgojIEFjY3VyYWN5IE1ldHJpY3MKYWNjdXJhY3kgPC0gc3VtKGRpYWcoY29uZl9tYXRyaXgpKSAvIHN1bShjb25mX21hdHJpeCkKcmVjYWxsIDwtIGNvbmZfbWF0cml4WzIsMl0gLyBzdW0oY29uZl9tYXRyaXhbMixdKQpzcGVjaWZpY2l0eSA8LSBjb25mX21hdHJpeFsxLDFdIC8gc3VtKGNvbmZfbWF0cml4WzEsXSkKCmNhdCgiQWNjdXJhY3k6IiwgYWNjdXJhY3ksICJcbiIpCmNhdCgiUmVjYWxsOiIsIHJlY2FsbCwgIlxuIikKY2F0KCJTcGVjaWZpY2l0eToiLCBzcGVjaWZpY2l0eSwgIlxuIikKYGBgCg==